package dovs;

import dovs.node.*;
import java.util.*;
import java.io.*;

/** Methods and intertype declarations having to do with
 *  error handling.
 */
public class Errors {
	/** Which errors has occured */
	private static LinkedList<ErrorType> errors = new LinkedList<ErrorType>();

	/**
	 * Report an error at a specific position in a source file.
	 * 
	 * @param type
	 *            the type of the error
	 * @param pos
	 *            the position at which the error occurred
	 * @param message
	 *            the error message
	 */
	public static void error(ErrorType type, Token pos, String message,
			boolean fatal) {
		if (type == null) {
			throw new InternalCompilerError("Error type was null for error: "
					+ message);
		}
		if (pos == null) {
			throw new InternalCompilerError(
					"Error position was null for error: " + message);
		}

		ASourceFile sf = pos.getAncestor(ASourceFile.class);
		if (sf == null) {
			PTypeDecl typed = pos.getAncestor(PTypeDecl.class);
			if (typed == null) {
				System.err.println("Error in unknown place:");
			} else {
				String kind = typed instanceof AInterfaceTypeDecl ? "interface"
						: "class";
				String name = typed.canonical_name != null ? typed.canonical_name
						: typed.getName().getText();
				System.err.println("Error in " + kind + " " + name + ":");
			}
		} else {
			System.err.println("Error at " + sf.getFileName() + ":"
					+ pos.getLine() + "." + pos.getPos() + ":");
			if (pos.getLine() > 0 && pos.getPos() > 0) {
				try {
					BufferedReader br = new BufferedReader(new FileReader(sf.getFileName()));
					String line = br.readLine();
					int lineno = 1;
					while (line != null) {
						if (lineno == pos.getLine()) {
							if (line.length() < pos.getPos()) {
								break;
							}
							StringBuffer sb1 = new StringBuffer();
							StringBuffer sb2 = new StringBuffer();
							for (int i = 0 ; i < line.length() ; i++) {
								if (pos.getPos() == i+1) {
									sb2.append('^');
								} else {
									sb2.append(' ');
								}
								if (line.charAt(i) == '\t') {
									int tab = sb1.length()%4;
									if (tab == 0) {
										tab = 4;
									}
									for (int j = 0 ; j < tab-1 ; j++) {
										sb1.append(' ');
										sb2.append(' ');
									}
									sb1.append(' ');
								} else {
									sb1.append(line.charAt(i));
								}
							}
							System.err.println(sb1.toString());
							System.err.println(sb2.toString());
							break;
						}
						lineno++;
						line = br.readLine();
					}
					br.close();
				} catch (IOException e) {
					// System.err.println(e.getMessage());
				}
			}
		}
		System.err.println(message);

		errors.add(type);
		if (fatal) {
			check();
		}
	}

	/**
	 * If the {@code -joos1} option is set, an error for the use of a Joos2
	 * feature is reported.
	 * 
	 * @param type
	 *            the type of the error
	 * @param pos
	 *            the position at which the error occured
	 * @param feature
	 *            the name of the Joos2 feature used
	 */
	public static void checkJoos1(ErrorType type, Token pos, String feature) {
		if (dovs.Main.joos1) {
			error(type, pos, "Joos 2 feature used: " + feature, false);
		}
	}

	/**
	 * Report an error not associated with a syntax node, such as a file error
	 * or parse error.
	 * 
	 * @param type
	 *            the type of the error
	 * @param message
	 *            the error message
	 */
	public static void errorMessage(ErrorType type, String message) {
		if (type == null) {
			throw new InternalCompilerError("Error type was null for error: "
					+ message);
		}
		System.err.println(message);

		errors.add(type);
	}

	/**
	 * Report an error not associated with a syntax node, such as a file error
	 * or parse error, and stop the compilation.
	 * 
	 * @param type
	 *            the type of the error
	 * @param message
	 *            the error message
	 */
	public static void fatalErrorMessage(ErrorType type, String message) {
		errorMessage(type, message);
		check();
	}

	/**
	 * Check if any errors have occurred yet. Called between each compiler phase
	 * to stop the compilation if errors occurred during the previous phase.
	 * 
	 * @exception dovs.SourceError
	 *                if any error has occurred.
	 */
	public static void check() {
		if (!errors.isEmpty())
			throw new SourceError(errors);
	}

	/** Reset the error flag for a fresh compilation */
	public static void reset() {
		errors.clear();
	}
}
